﻿namespace Rsdn.Janus
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using CoreServices.Extensibility;
    using DataModel;
    using SmartApp;
    using TreeGrid;

    /// <summary>
    /// Сообщение форума.
    /// </summary>
    public class Msg : MsgBase
    {
        private const int _frameSize = 200;

        private bool _partiallyLoaded;
        private int _loadIndex;

        public Msg(IServiceProvider provider)
            : base(provider)
        {
        }

        protected override IMsg GetTopic()
        {
            IMsg curr = this;
            if (curr.Parent != null)
            {
                while (curr.Parent.Parent != null)
                {
                    curr = curr.Parent;
                }
            }

            return curr;
        }

        protected override int GetTopicId()
        {
            return GetTopic().ID;
        }

        #region IGetData - реализация интерфейса.
        protected override void GetDataExt(CellInfo[] aryCellData)
        {
            aryCellData[EXT_INFO_COLUMN].Text = GetFormattedRatingForReplies();
        }

        public override void GetData(NodeInfo nodeInfo, CellInfo[] cellData)
        {
            if (_partiallyLoaded)
            {
                LoadFrame(this);
            }

            base.GetData(nodeInfo, cellData);
        }
        #endregion

        #region Работа с данными.


        // Заполняет дочерние ветки. Производит дочитку.
        protected override void FillChildren()
        {
            RepliesCount = 0;
            RepliesToMeUnread = 0;
            RepliesUnread = 0;
            RepliesMarked = 0;

            if (Children != null)
            {
                Children.Clear();
            }

            //DatabaseManager.GetChildList(this);
            GetTopicChildren(ServiceProvider);
        }

        private void GetTopicChildren(IServiceProvider provider)
        {
			using (var db = provider.CreateDBContext())
            {
                var messages = db.Messages(m => m.TopicID == ID).OrderBy(m => m.ParentID);

                var msgs = LoadMessages(provider, messages, this);

                // added children
                foreach (var msg in msgs)
                {
                    msg.EndMapping(null);
                }
            }
        }

        protected static List<MsgBase> LoadMessages(IServiceProvider provider, IOrderedQueryable<IForumMessage> messagesIn, MsgBase parent)
        {
            ISharedInfoService sharedInfoService = provider.GetRequiredService<ISharedInfoService>();
            IForumDisplayConfig forumDisplayConfig = sharedInfoService.GetObjValue(SharedInfoKeys.ForumDisplayConfig) as IForumDisplayConfig;

            var messages =
                forumDisplayConfig != null && forumDisplayConfig.NewMessagesOnTop
                    ? messagesIn.ThenByDescending(m => m.Date)
                    : messagesIn.ThenBy(m => m.Date);
            var msgs =
                messages
                    .Select(
                        m =>
                        new Msg(provider)
                            {
                                Parent = parent,
                                IsChild = true,

                                ID = m.ID,
                                ParentID = m.ParentID,
                                ForumID = m.ForumID,
                                Name = m.Name,
                                Date = m.Date,
                                Subject = m.Subject,
                                UserID = m.UserID,
                                UserClass = (short)m.UserClass,
                                UserNick = m.UserNick,
                                IsRead = m.IsRead,
                                IsMarked = m.IsMarked,
                                Closed = m.Closed,
                                ReadReplies = m.ReadReplies,
                                LastModerated = m.LastModerated,
                                ArticleId = m.ArticleId,
                                Rating = m.Rating(),
                                Smiles = m.SmileCount(),
                                Agrees = m.AgreeCount(),
                                Disagrees = m.DisagreeCount(),
                                Moderatorials = m.ActiveModeratorialCount()
                            })
                    .Cast<MsgBase>()
                    .ToList();


            return msgs;
        }

        public static IMsg FilterFirstLevel(IServiceProvider provider, Msg curMsg, string text)
        {
            var msg = new Msg(provider);
            var children =
                curMsg
                    .Children
                    .Where(
                        msgBase =>
                            msgBase
                                .Subject
                                .ToLower()
                                .IndexOf(text.ToLower()) != -1)
                    .ToList();

            msg.Children = children;

            return msg;
        }
        #endregion

        #region Загрузка топиков - кейсет.
        public override NodeFlags Flags
        {
            get
            {
                if (_partiallyLoaded)
                {
                    LoadFrame(this);
                }

                return base.Flags;
            }

            set
            {
                base.Flags = value;
            }
        }

        public override bool HasChildren
        {
            get
            {
                if (_partiallyLoaded)
                {
                    LoadFrame(this);
                }

                return base.HasChildren;
            }
        }

        private static IQueryable<int> GetKeysetSource(IJanusDataContext db, int forumID, SortType sort)
        {
            if (sort == SortType.ByLastUpdateDateAsc || sort == SortType.ByLastUpdateDateDesc)
            {
                var topicSrc = db.TopicInfos(ti => ti.ForumID == forumID);
                topicSrc =
                    sort == SortType.ByLastUpdateDateAsc
                        ? topicSrc.OrderBy(ti => ti.LastUpdateDate)
                        : topicSrc.OrderByDescending(ti => ti.LastUpdateDate);
                return topicSrc.Select(ti => ti.MessageID);
            }

            var msgSrc = db.Messages(m => m.TopicID == 0 && m.ForumID == forumID);
            switch (sort)
            {
                case SortType.ByDateAsc:
                    msgSrc = msgSrc.OrderBy(m => m.Date);
                    break;
                case SortType.ByDateDesc:
                    msgSrc = msgSrc.OrderByDescending(m => m.Date);
                    break;
                case SortType.ByIdAsc:
                    msgSrc = msgSrc.OrderBy(m => m.ID);
                    break;
                case SortType.ByIdDesc:
                    msgSrc = msgSrc.OrderByDescending(m => m.ID);
                    break;
                case SortType.BySubjectAsc:
                    msgSrc = msgSrc.OrderBy(m => m.Subject);
                    break;
                case SortType.BySubjectDesc:
                    msgSrc = msgSrc.OrderByDescending(m => m.Subject);
                    break;
                case SortType.ByAuthorAsc:
                    msgSrc = msgSrc.OrderBy(m => m.UserNick);
                    break;
                case SortType.ByAuthorDesc:
                    msgSrc = msgSrc.OrderByDescending(m => m.UserNick);
                    break;
                case SortType.ByForumAsc:
                    msgSrc = msgSrc.OrderBy(m => m.ServerForum.Name);
                    break;
                case SortType.ByForumDesc:
                    msgSrc = msgSrc.OrderByDescending(m => m.ServerForum.Name);
                    break;
                default:
                    throw new ArgumentOutOfRangeException("sort");    //MLHIDE
            }

            return msgSrc.Select(m => m.ID);
        }

        public static Msg GetTopicsKeyset(IServiceProvider provider, int forumID, SortType sort, bool isLoadAll)
        {
            ISharedInfoService sharedInfoService = provider.GetRequiredService<ISharedInfoService>();
            IForumDisplayConfig forumDisplayConfig =
                sharedInfoService.GetObjValue(SharedInfoKeys.ForumDisplayConfig) as IForumDisplayConfig;

            var virtualRoot = new Msg(provider);
            using (IJanusDataContext db = provider.CreateDBContext())
            {
                IQueryable<int> src = GetKeysetSource(db, forumID, sort);
                //var displayConfig = Config.Instance.ForumDisplayConfig;
                if (forumDisplayConfig != null && !(isLoadAll || forumDisplayConfig.MaxTopicsPerForum <= 0))
                {
                    src = src.Take(forumDisplayConfig.MaxTopicsPerForum);
                }

                virtualRoot.Children =
                    src
                        .Select(
                            id =>
                                new Msg(provider)
                                {
                                    ID = id,
                                    ParentID = 0,
                                    ForumID = forumID,
                                    Parent = virtualRoot,
                                    IsChild = false,
                                    _partiallyLoaded = true
                                })
                        .Cast<MsgBase>()
                        .ToList();
                var idx = 0;
                foreach (Msg msg in virtualRoot.Children)
                {
                    msg._loadIndex = idx;
                    idx++;
                }
            }

            return virtualRoot;
        }

        private static void LoadFrame(Msg msg)
        {
            var children = ((Msg)msg.Parent).Children;
            var frameTop = msg._loadIndex / _frameSize * _frameSize;
            var ids =
                Enumerable
                    .Range(frameTop, Math.Min(_frameSize, children.Count - frameTop))
                    .Select(i => children[i].ID)
                    .ToArray();

            var provider = msg.ServiceProvider;
			using (var db = provider.CreateDBContext())
            {
                var msgs =
                    db
                        .TopicInfos(ti => ids.Contains(ti.MessageID))
                        .Select(
                            ti =>
                                new
                                {
                                    ti.Message.Name,
                                    ti.Message.Date,
                                    ti.Message.Subject,
                                    ti.Message.UserID,
                                    UserClass = (short)ti.Message.UserClass,
                                    ti.Message.UserNick,
                                    ti.Message.IsRead,
                                    ti.Message.IsMarked,
                                    ti.Message.Closed,
                                    ti.Message.ReadReplies,
                                    ti.Message.LastModerated,
                                    ti.Message.ArticleId,
                                    Rating = ti.SelfRates,
                                    Smiles = ti.SelfSmiles,
                                    Agrees = ti.SelfAgrees,
                                    Disagrees = ti.SelfDisagrees,
                                    Moderatorials = ti.SelfModeratorials,
                                    RepliesCount = ti.AnswersCount,
                                    RepliesUnread = ti.AnswersUnread,
                                    RepliesRate = ti.AnswersRates,
                                    RepliesSmiles = ti.AnswersSmiles,
                                    RepliesAgree = ti.AnswersAgrees,
                                    RepliesDisagree = ti.AnswersDisagrees,
                                    RepliesToMeUnread = ti.AnswersToMeUnread,
                                    RepliesMarked = ti.AnswersMarked,
                                    RepliesModeratorials = ti.AnswersModeratorials
                                });
                var i = frameTop;
                foreach (var tuple in msgs)
                {
                    var m = (Msg)children[i];

                    m.Name = tuple.Name;
                    m.Date = tuple.Date;
                    m.Subject = tuple.Subject;
                    m.UserID = tuple.UserID;
                    m.UserClass = tuple.UserClass;
                    m.UserNick = tuple.UserNick;
                    m.IsRead = tuple.IsRead;
                    m.IsMarked = tuple.IsMarked;
                    m.Closed = tuple.Closed;
                    m.ReadReplies = tuple.ReadReplies;
                    m.LastModerated = tuple.LastModerated;
                    m.ArticleId = tuple.ArticleId;
                    m.Rating = tuple.Rating;
                    m.Smiles = tuple.Smiles;
                    m.Agrees = tuple.Agrees;
                    m.Disagrees = tuple.Disagrees;
                    m.Moderatorials = tuple.Moderatorials;
                    m.RepliesCount = tuple.RepliesCount;
                    m.RepliesUnread = tuple.RepliesUnread;
                    m.RepliesRate = tuple.RepliesRate;
                    m.RepliesSmiles = tuple.RepliesSmiles;
                    m.RepliesAgree = tuple.RepliesAgree;
                    m.RepliesDisagree = tuple.RepliesDisagree;
                    m.RepliesToMeUnread = tuple.RepliesToMeUnread;
                    m.RepliesMarked = tuple.RepliesMarked;
                    m.RepliesModeratorials = tuple.RepliesModeratorials;

                    m._partiallyLoaded = false;
                    m.EndMapping(null);
                    i++;
                }
            }
        }
        #endregion

        #region Original topics loading
        public static Msg GetTopics(IServiceProvider provider, int forumID, SortType sort, bool isLoadAll)
        {
            var msg = new Msg(provider);
            msg.Children = GetOldStyleTopicList(provider, forumID, sort, isLoadAll, msg);
            return msg;
        }

        public static IQueryable<ITopicInfo> AppendSortPredicate(IQueryable<ITopicInfo> source, SortType sort)
        {
            switch (sort)
            {
                case SortType.ByLastUpdateDateDesc:
                    return source.OrderByDescending(ti => ti.LastUpdateDate);
                case SortType.ByLastUpdateDateAsc:
                    return source.OrderBy(ti => ti.LastUpdateDate ?? ti.Message.Date);
                case SortType.ByDateAsc:
                    return source.OrderBy(ti => ti.Message.Date);
                case SortType.ByDateDesc:
                    return source.OrderByDescending(ti => ti.Message.Date);
                case SortType.ByIdAsc:
                    return source.OrderBy(ti => ti.MessageID);
                case SortType.ByIdDesc:
                    return source.OrderByDescending(ti => ti.MessageID);
                case SortType.BySubjectAsc:
                    return source.OrderBy(ti => ti.Message.Subject);
                case SortType.BySubjectDesc:
                    return source.OrderByDescending(ti => ti.Message.Subject);
                case SortType.ByAuthorAsc:
                    return source.OrderBy(ti => ti.Message.UserNick);
                case SortType.ByAuthorDesc:
                    return source.OrderByDescending(ti => ti.Message.UserNick);
                //case SortType.ByForumAsc:
                //case SortType.ByForumDesc:
                default:
                    throw new ArgumentOutOfRangeException("sort");    //MLHIDE
            }
        }

        private static List<MsgBase> GetOldStyleTopicList(IServiceProvider provider, int forumId, SortType sort, bool loadAll,
            MsgBase parent)
        {
            ISharedInfoService sharedInfoService = provider.GetRequiredService<ISharedInfoService>();
            IForumDisplayConfig forumDisplayConfig =
                sharedInfoService.GetObjValue(SharedInfoKeys.ForumDisplayConfig) as IForumDisplayConfig;

			using (var db = provider.CreateDBContext())
            {
                //ForumDisplayConfig displayConfig = Config.Instance.ForumDisplayConfig;
                IQueryable<ITopicInfo> query =
                    db.TopicInfos(ti => (ti.ForumID == forumId));

                if (forumDisplayConfig != null)
                {
                    if (forumDisplayConfig.ShowUnreadThreadsOnly)
                    {
                        // order is important for SQL generation (Possible error in BLToolkit)
                        query = query.Where(ti => ti.AnswersUnread > 0 || !ti.Message.IsRead);
                    }

                    if (!(loadAll || forumDisplayConfig.MaxTopicsPerForum <= 0))
                    {
                        query = query.Take(forumDisplayConfig.MaxTopicsPerForum);
                    }
                }

                query = AppendSortPredicate(query, sort);

                IQueryable<Msg> queryable = query
                    .Select(
                        ti =>
                        new Msg(provider)
                            {
                                Parent = parent,
                                IsChild = false,

                                ID = ti.MessageID,
                                ParentID = 0,
                                ForumID = ti.ForumID,
                                Name = ti.Message.Name,
                                Date = ti.Message.Date,
                                Subject = ti.Message.Subject,
                                UserID = ti.Message.UserID,
                                UserClass = (short)ti.Message.UserClass,
                                UserNick = ti.Message.UserNick,
                                IsRead = ti.Message.IsRead,
                                IsMarked = ti.Message.IsMarked,
                                Closed = ti.Message.Closed,
                                ReadReplies = ti.Message.ReadReplies,
                                LastModerated = ti.Message.LastModerated,
                                ArticleId = ti.Message.ArticleId,
                                Rating = ti.SelfRates,
                                Smiles = ti.SelfSmiles,
                                Agrees = ti.SelfAgrees,
                                Disagrees = ti.SelfDisagrees,
                                Moderatorials = ti.SelfModeratorials,
                                RepliesCount = ti.AnswersCount,
                                RepliesUnread = ti.AnswersUnread,
                                RepliesRate = ti.AnswersRates,
                                RepliesSmiles = ti.AnswersSmiles,
                                RepliesAgree = ti.AnswersAgrees,
                                RepliesDisagree = ti.AnswersDisagrees,
                                RepliesToMeUnread = ti.AnswersToMeUnread,
                                RepliesMarked = ti.AnswersMarked,
                                RepliesModeratorials = ti.AnswersModeratorials
                            });

                List<MsgBase> msgs = queryable.Cast<MsgBase>().ToList();
                foreach (var msg in msgs)
                {
                    msg.EndMapping(null);
                }

                return msgs;
            }
        }
        #endregion

    }
}